﻿#pragma once

#include <hacks/base/ev_loop.h>

namespace hacks {
    template<typename>
    class ev_fd;

    template<typename T>
    using shared_ev_fd = std::shared_ptr<ev_fd<T>>;

    using ev_sock_fd = ev_fd<ev_os_sock_t>;
    using ev_file_fd = ev_fd<ev_os_fd_t>;

    SHARED_TYPE(ev_sock_fd);
    SHARED_TYPE(ev_file_fd);

    template<typename T>
    using ev_fd_event_functor_t = std::function<void(ev_fd<T> &, int status, int evt)>;

    template<typename T>
    using ev_fd_closed_functor_t = std::function<void(ev_fd<T> &)>;

    typedef enum {
        ev_event_none = 0,
        ev_event_readable = 1,
        ev_event_writeable = 2
    } ev_io_event;

    template<typename T>
    class ev_fd : public std::enable_shared_from_this<ev_fd<T>> {
        typedef ev_buddy <ev_fd> ev_sock_fd_buddy;

    DISABLE_COPY_MOVE(ev_fd)

    public:

        ~ev_fd() {
            delete poll_;
        }

        ev_fd(const shared_ev_loop &loop,
              T os_fd,
              ev_fd_event_functor_t<T> &&event_functor,
              ev_fd_closed_functor_t<T> &&closed_functor) :
                event_loop_(loop),
                poll_(nullptr),
                os_fd_(os_fd),
                event_functor_(std::forward<ev_fd_event_functor_t<T>>(event_functor)),
                closed_functor_(std::forward<ev_fd_closed_functor_t<T>>(closed_functor)),
                commit_once_flag_(),
                fd_status_(evt_init),
                io_event_(ev_event_none),
                user_data_(nullptr) {
            assert(event_loop_);
            assert(event_functor_);
        }

        ev_fd(const shared_ev_loop &loop,
              T os_fd,
              ev_fd_event_functor_t<T> &&event_functor) :
                ev_fd(loop, os_fd,
                      std::forward<ev_fd_event_functor_t<T>>(event_functor),
                      nullptr) {
        }

        void commit() {
            std::call_once(commit_once_flag_, [this]() {
                auto ins = this->shared_from_this();
                assert(ins);
                event_loop_->run_in_loop([ins]() {
                    ins->init();
                    auto *buddy = new ev_sock_fd_buddy;
                    buddy->shd_ins = ins;
                    ins->poll_->data = buddy;
                    ins->update_event(ins->sock_event_);
                });
                fd_status_.store(evt_commited, std::memory_order_release);
            });
        }

        void close() {
            ev_evt_state exp = evt_commited;
            if (!fd_status_.compare_exchange_strong(
                    exp,
                    evt_stopping,
                    std::memory_order_acq_rel)) {
                return;
            }
            event_loop_->run_in_loop([ins = this->shared_from_this()]() {
                //stop uv_poll
                uv_poll_stop(ins->poll_);
                //close this poll handler
                uv_close(reinterpret_cast<uv_handle_t *>(ins->poll_), &ev_fd::ev_fd_poll_closed_callback);
            });
        }

        EV_LOOP_CALL void update_event(int event) const {
            assert(event_loop_->in_event_loop());
            EQ_OR_RETURN(fd_status_, evt_commited);
            uv_poll_start(poll_, io_event_, &ev_fd::ev_fd_poll_callback);
        }

        ev_evt_state get_status() const {
            return fd_status_.load(std::memory_order_acquire);
        }

        //get fd
        T get_os_fd() const {
            return os_fd_;
        }

        void set_userdata(void *udata) {
            user_data_ = udata;
        }

        void *get_userdata() const {
            return user_data_;
        }

        shared_ev_loop get_loop() const {
            return event_loop_;
        }

        ev_io_event get_current_event() const {
            return io_event_;
        }

    private:
        EV_LOOP_CALL void init() {
            poll_ = new uv_poll_t;
            constexpr auto poll_init_fnc = std::is_same<T, ev_os_sock_t>::value ? uv_poll_init_socket : uv_poll_init;
            poll_init_fnc(event_loop_->get_raw_loop(), poll_, os_fd_);
        }

        EV_LOOP_CALL static void ev_fd_poll_callback(uv_poll_t *poll, int status, int events) {
            auto *buddy = static_cast<ev_sock_fd_buddy *>(poll->data);
            assert(buddy);
            const auto &ins = buddy->shd_ins;
            ins->event_functor_(*ins, status, events);
        }

        EV_LOOP_CALL static void ev_fd_poll_closed_callback(uv_handle_t *h) {
            auto *buddy = static_cast<ev_sock_fd_buddy *>(h->data);
            auto &closed_functor = buddy->shd_ins->closed_functor_;
            buddy->shd_ins->fd_status_.store(evt_stopped, std::memory_order_release);
            if (closed_functor) {
                closed_functor(*buddy->shd_ins);
            }
            delete buddy;
        }

    private:
        shared_ev_loop event_loop_;
        uv_poll_t *poll_;
        T os_fd_;
        ev_fd_event_functor_t<T> event_functor_;
        ev_fd_closed_functor_t<T> closed_functor_;
        std::once_flag commit_once_flag_;
        std::atomic<ev_evt_state> fd_status_;
        ev_io_event io_event_;
        void *user_data_;
    };

}